package org.elasticsearch.plugin.index.query.cnn;

import java.io.IOException;

import org.apache.lucene.search.Query;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.plugin.index.mapper.cnn.CNNMapper;
import org.elasticsearch.plugin.index.mapper.cnn.HashEnum;
import org.elasticsearch.plugin.util.SerializationUtils;

import org.elasticsearch.plugin.util.*;

public class LSHQueryParser implements QueryParser {

	public static final String NAME = "cnn";

	private Client client;

	@Inject
	public LSHQueryParser(Client client) {
		this.client = client;
	}

	@Override
	public String[] names() {
		// TODO Auto-generated method stub
		return new String[] { NAME };
	}

	@Override
	public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
		// TODO Auto-generated method stub
		XContentParser parser = parseContext.parser();
		XContentParser.Token token = parser.nextToken();
		if (token != XContentParser.Token.FIELD_NAME) {
			throw new QueryParsingException(parseContext.index(), "[cnn] query malformed, no field");
		}
		String fieldName = parser.currentName();
		String feature = null;
		HashEnum hashEnum = null;
		double[] data = null;
		float boost = 1.0f;
		int limit = -1;

		String lookupIndex = parseContext.index().name();
		String lookupType = null;

		token = parser.nextToken();
		if (token == XContentParser.Token.START_OBJECT) {
			String currentFieldName = null;
			while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
				if (token == XContentParser.Token.FIELD_NAME) {
					currentFieldName = parser.currentName();
				} else {
					if ("feature".equals(currentFieldName)) {
						feature = parser.text();
					} else if ("CNN".equals(currentFieldName)) {
						data = SerializationUtils.toDoubleArray(parser.text());
					} else if ("hash".equals(currentFieldName)) {
						hashEnum = HashEnum.getByName(parser.text());
					} else if ("limit".equals(currentFieldName)) {
						limit = parser.intValue();
					} else if ("index".equals(currentFieldName)) {
						lookupIndex = parser.text();
					} else if ("type".equals(currentFieldName)) {
						lookupType = parser.text();
					} else {
						throw new QueryParsingException(parseContext.index(),
								"[cnn] query does not support [" + currentFieldName + "]");
					}
				}
			}
			parser.nextToken();
		}
		if(feature == null || data == null){
			throw new QueryParsingException(parseContext.index(), "No feature specified for image query");
		}
		
		String luceneFieldName = fieldName + "." + feature;
		if(hashEnum == null){
			//return new ImageQuery(luceneFieldName, feature, boost);
			return null;
		}
		else {  // query by hash first
            int[] hash = null;
            if (hashEnum.equals(HashEnum.BIT_SAMPLING)) {
                hash = BitSample.generateHashes(data);
            } else if (hashEnum.equals(HashEnum.LSH)) {
                hash = LocalitySensitiveHash.generateHashes(data);
            }
            String hashFieldName = luceneFieldName + "." + CNNMapper.HASH + "." + hashEnum.name();

            if (limit > 0) {  // has max result limit, use ImageHashLimitQuery
                return new CNNHashLimitQuery(hashFieldName, hash, limit, luceneFieldName, data, boost);
            } else {  // no max result limit, use ImageHashQuery
               /* BooleanQuery query = new BooleanQuery(true);
                ImageScoreCache imageScoreCache = new ImageScoreCache();

                for (int h : hash) {
                    query.add(new BooleanClause(new ImageHashQuery(new Term(hashFieldName, Integer.toString(h)), luceneFieldName, feature, imageScoreCache, boost), BooleanClause.Occur.SHOULD));
                }
                return query;*/
        		return null;
            }
        }
	}

}
